package com.gitee.qdbp.jdbc.sql;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.jdbc.core.SqlParameterValue;
import com.gitee.qdbp.able.enums.WrapMode;
import com.gitee.qdbp.able.jdbc.model.DbFieldName;
import com.gitee.qdbp.able.jdbc.model.DbFieldValue;
import com.gitee.qdbp.able.jdbc.model.DbRawValue;
import com.gitee.qdbp.jdbc.model.SimpleFieldColumn;
import com.gitee.qdbp.jdbc.model.TypedDbVariable;
import com.gitee.qdbp.jdbc.sql.SqlBuffer.Item;
import com.gitee.qdbp.jdbc.sql.SqlBuffer.OmitItem;
import com.gitee.qdbp.jdbc.sql.SqlBuffer.RawValueItem;
import com.gitee.qdbp.jdbc.sql.SqlBuffer.StringItem;
import com.gitee.qdbp.jdbc.sql.SqlBuffer.VariableItem;
import com.gitee.qdbp.tools.utils.IndentTools;
import com.gitee.qdbp.tools.utils.NamingTools;
import com.gitee.qdbp.tools.utils.StringTools;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 与数据库方言无关的SQL静态工具类
 *
 * @author zhaohuihua
 * @version 190601
 */
public class SqlTools {

    /** 工具类私有构造方法 **/
    private SqlTools() {
    }

    public static Object unwrapDbVariable(Object value) {
        if (value instanceof TypedDbVariable) {
            return ((TypedDbVariable) value).getValue();
        } else if (value instanceof SqlParameterValue) {
            return ((SqlParameterValue) value).getValue();
        } else if (value instanceof DbFieldName) {
            return ((DbFieldName) value).getFieldName();
        } else if (value instanceof DbFieldValue) {
            return ((DbFieldValue) value).getFieldValue();
        } else if (value instanceof DbRawValue) {
            return value.toString();
        } else {
            return value;
        }
    }

    public static Object wrapDbVariable(SimpleFieldColumn column, Object value) {
        if (value instanceof DbFieldName || value instanceof DbFieldValue || value instanceof DbRawValue) {
            return value;
        } else if (value instanceof TypedDbVariable || value instanceof SqlParameterValue) {
            return value;
        } else if (column.getJdbcType() != null) {
            return new TypedDbVariable(column.getJdbcType(), value);
        } else {
            return value;
        }
    }

    /**
     * 转换为下划线命名字符串<br>
     * 如 u.userName -- u.USER_NAME
     * 
     * @param fieldName 字段名
     * @param useUpperCase 是否使用大写
     * @return 下划线命名字符串
     */
    public static String toUnderlineString(String fieldName, boolean useUpperCase) {
        int dotIndex = fieldName.indexOf('.');
        if (dotIndex <= 0) {
            String underlineString = NamingTools.toUnderlineString(fieldName);
            return useUpperCase ? underlineString.toUpperCase() : underlineString;
        } else {
            String prefix = fieldName.substring(0, dotIndex);
            String suffix = fieldName.substring(dotIndex + 1);
            String underlineSuffix = NamingTools.toUnderlineString(suffix);
            return prefix + '.' + (useUpperCase ? underlineSuffix.toUpperCase() : underlineSuffix);
        }
    }

    public static String getFieldName(String columnName, String tableAliasSeparator) {
        // 表别名与列别名的分隔符
        int tableAliasIndex = columnName.indexOf(tableAliasSeparator);
        if (tableAliasIndex >= 0) {
            columnName = columnName.substring(tableAliasIndex + tableAliasSeparator.length());
        }
        return NamingTools.toCamelString(columnName);
    }

    public static List<?> duplicateRemoval(Collection<?> items) {
        Map<Object, ?> map = new HashMap<>();
        List<Object> list = new ArrayList<>();
        for (Object item : items) {
            if (!map.containsKey(item)) {
                map.put(item, null);
                list.add(item);
            }
        }
        return list;
    }

    /**
     * 前后增加括号
     * 
     * @param sql SQL语句
     * @param brackets 括号处理方式
     */
    public static void wrap(SqlBuffer sql, WrapMode brackets) {
        wrap(sql, brackets, null, null, null, null, true);
    }

    /**
     * 增加前缀/后缀
     * 
     * @param sql SQL语句
     * @param brackets 括号处理方式
     * @param prefix 待插入的前缀
     * @param suffix 待插入的后缀
     */
    public static void wrap(SqlBuffer sql, WrapMode brackets, String prefix, String suffix) {
        wrap(sql, brackets, prefix, null, suffix, null, true);
    }

    /**
     * 增加前缀/后缀
     * 
     * @param sql SQL语句
     * @param brackets 括号处理方式
     * @param prefix 待插入的前缀
     * @param prefixOverrides 待替换的前缀, 不区分大小写, 支持以|拆分的多个前缀, 如AND|OR
     * @param suffix 待插入的后缀
     * @param suffixOverrides 待替换的后缀, 不区分大小写, 支持以|拆分的多个后缀, 如AND|OR
     */
    public static void wrap(SqlBuffer sql, WrapMode brackets, String prefix, String prefixOverrides, String suffix,
            String suffixOverrides) {
        wrap(sql, brackets, prefix, prefixOverrides, suffix, suffixOverrides, true);
    }

    /**
     * 增加前缀/后缀
     * 
     * @param sql SQL语句
     * @param brackets 括号处理方式
     * @param prefix 待插入的前缀
     * @param prefixOverrides 待替换的前缀, 不区分大小写, 支持以|拆分的多个前缀, 如AND|OR
     * @param suffix 待插入的后缀
     * @param suffixOverrides 待替换的后缀, 不区分大小写, 支持以|拆分的多个后缀, 如AND|OR
     * @param addLeadingNewline 要不要添加前置换行符
     */
    public static void wrap(SqlBuffer sql, WrapMode brackets, String prefix, String prefixOverrides, String suffix,
            String suffixOverrides, boolean addLeadingNewline) {
        if (sql.isBlank()) {
            return;
        }
        boolean wrapBrackets = false;
        Boolean useNewline = null;
        if (brackets == WrapMode.FORCE) {
            wrapBrackets = true;
        } else if (brackets == WrapMode.AUTO) { // 自动识别是否添加括号
            if (VerifyTools.isNotBlank(prefix) && StringTools.existsWord(prefix, "OR")) {
                wrapBrackets = true; // 前缀条件是OR
            } else if (Text.exists(sql, "\n", true)) {
                wrapBrackets = true; // SQL中存在换行符
                useNewline = true;
            } else if (Text.exists(sql, "OR")) {
                wrapBrackets = true; // SQL中含有OR条件
            }
        }

        if (!wrapBrackets) {
            //\\\\\\\\\\\\\\\\\\\\
            // 不需要加括号
            //////////////////////
            if (VerifyTools.isNotBlank(prefix) || VerifyTools.isNotBlank(prefixOverrides)) {
                sql.insertPrefix(prefix, prefixOverrides);
            }
            if (VerifyTools.isNotBlank(suffix) || VerifyTools.isNotBlank(suffixOverrides)) {
                sql.insertSuffix(suffix, suffixOverrides);
            }
            return;
        }

        //\\\\\\\\\\\\\\\\\\\\
        // 需要加括号
        //////////////////////
        // 判断要不要换行
        if (useNewline == null) {
            useNewline = Text.exists(sql, "\n", true) || Text.exists(sql, "IN");
        }

        if (!useNewline) {
            //\\\\\\\\\\\\\\\\\\\\
            // 不需要换行
            //////////////////////
            // 前缀后缀加括号
            if (VerifyTools.isBlank(prefix)) {
                prefix = "( ";
            } else if (prefix.indexOf('(') >= 0) { // 前缀中存在左括号
                prefix += " ";
            } else {
                prefix += " ( ";
            }
            if (VerifyTools.isBlank(suffix)) {
                suffix = " )";
            } else if (suffix.indexOf(')') >= 0) { // 后缀中存在右括号
                prefix = " " + prefix;
            } else {
                prefix += " )";
            }

            sql.insertPrefix(prefix, prefixOverrides);
            sql.insertSuffix(suffix, suffixOverrides);
            return;
        }

        //\\\\\\\\\\\\\\\\\\\\
        // 需要换行
        //////////////////////
        // 先处理prefixOverrides/suffixOverrides
        if (VerifyTools.isNotBlank(prefixOverrides)) {
            sql.insertPrefix(null, prefixOverrides);
        }
        if (VerifyTools.isNotBlank(suffixOverrides)) {
            sql.insertSuffix(null, suffixOverrides);
        }

        // 计算buffer有多少个缩进
        int indent = Indent.countMinIndentSize(sql, false, 0);

        if (!Text.startsWithBlankline(sql)) {
            sql.prepend('\n');
        }
        // SQL内容增加缩进
        sql.tabAll(1, false);

        // 前缀后缀加括号和换行符
        String tabs = new String(IndentTools.space.getIndenTabs(indent));
        if (addLeadingNewline) {
            if (VerifyTools.isBlank(prefix)) {
                prefix = "\n" + tabs + "(";
            } else if (prefix.indexOf('(') >= 0) { // 前缀中存在左括号
                prefix = "\n" + tabs + prefix;
            } else {
                prefix = "\n" + tabs + prefix + " (";
            }
        } else {
            if (VerifyTools.isBlank(prefix)) {
                prefix = tabs + "(";
            } else if (prefix.indexOf('(') >= 0) { // 前缀中存在左括号
                prefix = tabs + prefix;
            } else {
                prefix = tabs + prefix + " (";
            }
        }
        if (VerifyTools.isBlank(suffix)) {
            suffix = "\n" + tabs + ")";
        } else if (suffix.indexOf(')') >= 0) { // 后缀中存在右括号
            suffix = "\n" + tabs + suffix;
        } else {
            suffix = "\n" + tabs + ") " + suffix;
        }

        sql.prepend(prefix);
        sql.append(suffix);
    }

    public abstract static class Indent {

        /**
         * 查找最后的缩进量<pre>
         * 例如: \r\n=换行符, \t=TAB符, \s=空格
         * \n\tABC\n\t\tDEF\t\t\t --> 这里要找的是DEF之前的那个换行符之后的空白字符, 即缩进量为2
         * \n\tABC\n\t\tDEF\n     --> 最后一个字符就是换行符, 即刚刚换行完, 要找的仍然是DEF之前的那个换行符
         * \n\tABC\n\t\tDEF\n\n   --> 最后连续多个换行符, 要找的仍然是DEF之前的那个换行符
         * \n\tABC\n\t\t          --> 这里应返回ABC后面的换行符之后的缩进量2
         * \tABC --> 这里应返回首行的缩进量1</pre>
         * 
         * @param sql SQL语句
         * @return 缩进量
         */
        public static int findLastIndentSize(SqlBuffer sql) {
            if (sql.items().isEmpty()) {
                return 0;
            }
            // 先从后向前查找带有换行符的字符串
            // 取换行符之后的子串
            // 如果子串全是空白字符, 再向后取前置的空白字符
            int size = sql.items().size();
            for (int i = size - 1; i >= 0; i--) {
                String suffixAfterNewline;
                { // 查找带有换行符的字符串, 并获取换行符之后的子串
                    CharSequence string = getItemStringValue(sql.items().get(i));
                    if (string == null) {
                        continue;
                    }
                    int lastIndex = string.length() - 1;
                    if (string.length() == 0) {
                        suffixAfterNewline = null;
                    } else {
                        if (i == size - 1) { // 如果是最后一项, 移除最后的连续多个换行符
                            lastIndex = getIndexOfBeforeTrailingChars(string, '\r', '\n');
                        }
                        // 获取换行符之后的子串
                        suffixAfterNewline = getSubstringOfAfterLastNewline(string, lastIndex);
                    }
                    if (suffixAfterNewline == null && i == 0) {
                        // 在没有换行符的情况下, 如果是第一项, 则将整个字符串视为换行符之后的子串
                        // +1是因为getIndexOfBeforeTrailingChars返回是的换行符之前的那个位置
                        // +1才能将那个位置的字符包含进来
                        suffixAfterNewline = string.toString().substring(0, lastIndex + 1);
                    }
                    if (suffixAfterNewline == null) {
                        continue; // 未找到换行符
                    }
                }
                // 已找到换行符, 判断是否全为空白字符
                if (!isAllWhitespace(suffixAfterNewline)) {
                    String leadingWhitespace = getLeadingWhitespace(suffixAfterNewline);
                    return IndentTools.calcSpacesToTabSize(leadingWhitespace);
                } else {
                    // 向后取前置的空白字符
                    StringBuilder buffer = new StringBuilder();
                    buffer.append(suffixAfterNewline);
                    for (int j = i + 1; j < size; j++) {
                        CharSequence string = getItemStringValue(sql.items().get(j));
                        if (string != null && isAllWhitespace(string)) {
                            buffer.append(string);
                            continue; // 后面仍然全是空白, 再继续向后查找
                        } else {
                            if (string != null && string.length() > 0) {
                                String leadingWhitespace = getLeadingWhitespace(string);
                                buffer.append(leadingWhitespace);
                            }
                            break; // 遇到非string类型的item, 或者不全是空白, 结束查找
                        }
                    }
                    return IndentTools.calcSpacesToTabSize(buffer);
                }
            }
            return 0;
        }

        /**
         * 清除最前面的缩进空白 (遇到换行或非空白字符就结束)
         * 
         * @param sql SQL语句
         * @return 清除了多少个缩进量
         */
        public static int clearLeadingIndentWhitespace(SqlBuffer sql) {
            if (sql.items().isEmpty()) {
                return 0;
            }
            // 先从前向后查找字符串
            // 取前置空白字符, 如果整个字符串全是空白字符, 再继续向后读取
            int size = sql.items().size();
            StringBuilder allCleared = new StringBuilder();
            for (int i = 0; i < size; i++) {
                Item item = sql.items().get(i);
                if (item instanceof StringItem) {
                    StringItem stringItem = (StringItem) item;
                    String cleared = clearLeadingIndentWhitespace(stringItem);
                    if (cleared != null) {
                        allCleared.append(cleared);
                    }
                    if (stringItem.getValue().length() > 0) {
                        break; // 清除完之后如果还有字符, 结束查找
                    }
                } else if (item instanceof VariableItem) {
                    break; // 遇到非string类型的item, 结束查找
                } else if (item instanceof RawValueItem) {
                    RawValueItem stringItem = (RawValueItem) item;
                    String cleared = clearLeadingIndentWhitespace(stringItem);
                    if (cleared != null) {
                        allCleared.append(cleared);
                    }
                    if (stringItem.getValue().length() > 0) {
                        break; // 清除完之后如果还有字符, 结束查找
                    }
                } else if (item instanceof OmitItem) {
                    continue;
                } else {
                    throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
                }
            }
            return IndentTools.calcSpacesToTabSize(allCleared);
        }

        private static String clearLeadingIndentWhitespace(StringItem item) {
            int index = 0;
            for (int i = 0, z = item.getValue().length() - 1; i < z; i++) {
                char c = item.getValue().charAt(i);
                if (Text.isInChars(c, ' ', '\t')) {
                    index++;
                } else {
                    break;
                }
            }
            if (index == 0) {
                return null;
            }
            String cleared = item.getValue().substring(0, index);
            item.getValue().delete(0, index);
            return cleared;
        }

        private static String clearLeadingIndentWhitespace(RawValueItem item) {
            int index = 0;
            for (int i = 0, z = item.getValue().length() - 1; i < z; i++) {
                char c = item.getValue().charAt(i);
                if (Text.isInChars(c, ' ', '\t')) {
                    index++;
                } else {
                    break;
                }
            }
            if (index == 0) {
                return null;
            }
            String cleared = item.getValue().substring(0, index);
            item.setValue(item.getValue().substring(index));
            return cleared;
        }

        /**
         * 清除最后的文字后面的缩进空白 (遇到换行或非空白字符就结束)
         * 
         * @param sql SQL语句
         * @return 清除了多少个缩进量
         */
        public static int clearTrailingIndentWhitespace(SqlBuffer sql) {
            if (sql.items().isEmpty()) {
                return 0;
            }
            // 先从后向前查找带有换行符的字符串
            // 取换行符之后的子串
            // 如果子串全是空白字符, 再向后取前置的空白字符
            int size = sql.items().size();
            StringBuilder allCleared = new StringBuilder();
            for (int i = size - 1; i >= 0; i--) {
                Item item = sql.items().get(i);
                if (item instanceof StringItem) {
                    StringItem stringItem = (StringItem) item;
                    String cleared = clearTrailingIndentWhitespace(stringItem);
                    if (cleared != null) {
                        allCleared.append(cleared);
                    }
                    if (stringItem.getValue().length() > 0) {
                        break; // 清除完之后如果还有字符, 结束查找
                    }
                } else if (item instanceof VariableItem) {
                    break; // 遇到非string类型的item, 结束查找
                } else if (item instanceof RawValueItem) {
                    RawValueItem stringItem = (RawValueItem) item;
                    String cleared = clearTrailingIndentWhitespace(stringItem);
                    if (cleared != null) {
                        allCleared.append(cleared);
                    }
                    if (stringItem.getValue().length() > 0) {
                        break; // 清除完之后如果还有字符, 结束查找
                    }
                } else if (item instanceof OmitItem) {
                    continue;
                } else {
                    throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
                }
            }
            return IndentTools.calcSpacesToTabSize(allCleared);
        }

        private static String clearTrailingIndentWhitespace(StringItem item) {
            // +1是因为getIndexOfBeforeTrailingChars返回是的空白之前的那个位置
            // +1才能将那个位置的字符包含进来
            int index = getIndexOfBeforeTrailingChars(item.getValue(), ' ', '\t') + 1;
            if (index >= item.getValue().length()) {
                return null;
            } else {
                String cleared = item.getValue().substring(index);
                item.getValue().setLength(index);
                return cleared;
            }
        }

        private static String clearTrailingIndentWhitespace(RawValueItem item) {
            // +1是因为getIndexOfBeforeTrailingChars返回是的空白之前的那个位置
            // +1才能将那个位置的字符包含进来
            int index = getIndexOfBeforeTrailingChars(item.getValue(), ' ', '\t') + 1;
            if (index >= item.getValue().length()) {
                return null;
            } else {
                String cleared = item.getValue().substring(index);
                item.setValue(index == 0 ? "" : item.getValue().substring(0, index));
                return cleared;
            }
        }

        /** 统计最小的缩进数, -1表示未找到缩进 **/
        public static int countMinIndentSize(SqlBuffer sql) {
            return countMinIndentSize(sql, true, -1);
        }

        /** 统计最小的缩进数, -1表示未找到缩进, defaults=未找到时的默认值 **/
        public static int countMinIndentSize(SqlBuffer sql, int defaults) {
            return countMinIndentSize(sql, true, defaults);
        }

        /**
         * 统计最小的缩进数, -1表示未找到缩进
         * 
         * @param sql SQL语句
         * @param leadingIndent 要不要统计开头的缩进
         * @return 缩进数
         */
        public static int countMinIndentSize(SqlBuffer sql, boolean leadingIndent) {
            return countMinIndentSize(sql, leadingIndent, -1);
        }

        /**
         * 统计最小的缩进数, -1表示未找到缩进
         * 
         * @param sql SQL语句
         * @param leadingIndent 要不要统计开头的缩进
         * @param defaultSize 未找到时的默认值
         * @return 缩进数
         */
        public static int countMinIndentSize(SqlBuffer sql, boolean leadingIndent, int defaultSize) {
            if (sql.items().isEmpty()) {
                return 0;
            }
            int minIndent = -1;
            boolean first = true;
            for (Item item : sql.items()) {
                if (item == null) {
                    continue;
                }
                CharSequence string;
                if (item instanceof StringItem) {
                    string = ((StringItem) item).getValue();
                } else if (item instanceof RawValueItem) {
                    string = ((RawValueItem) item).getValue();
                } else if (item instanceof VariableItem) {
                    if (first) {
                        return 0;
                    } else {
                        continue;
                    }
                } else if (item instanceof OmitItem) {
                    continue;
                } else {
                    throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
                }
                if (string == null || string.length() == 0) {
                    continue;
                }
                // blankLineIndent=是否统计空行的缩进, 一定要传true
                // 因为这里的string只是片段, 后面可能还有内容, 并不能判断空行
                int indent = IndentTools.countMinIndentSize(string, first && leadingIndent, true);
                first = false;
                if (indent < 0) {
                    continue;
                } else if (indent == 0) {
                    return 0; // 已找到最小缩进数量, 可以提前结束
                } else {
                    if (minIndent < 0 || minIndent > indent) {
                        minIndent = indent;
                    }
                }
            }
            return minIndent < 0 ? defaultSize : minIndent;
        }

        private static CharSequence getItemStringValue(Item item) {
            if (item instanceof StringItem) {
                return ((StringItem) item).getValue();
            } else if (item instanceof VariableItem) {
                return null;
            } else if (item instanceof RawValueItem) {
                return ((RawValueItem) item).getValue();
            } else if (item instanceof OmitItem) {
                return null;
            } else {
                throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
            }
        }

        /** 获取最后一个换行符之后的子字符串 **/
        // ABC\n             -- ""
        // ABC\n\t\t         -- \t\t
        // \n\t\tABC\t\t\t   -- \t\t (ABC前面的2个TAB)
        // \n\t\tABC\n\t\t\t -- \t\t\t (ABC后面的3个TAB)
        // \t\tABC           -- null (没有换行符)
        // ""                -- null (没有换行符)
        private static String getSubstringOfAfterLastNewline(CharSequence string, int lastIndex) {
            for (int i = lastIndex; i >= 0; i--) {
                char c = string.charAt(i);
                if (c == '\r' || c == '\n') {
                    return string.toString().substring(i + 1);
                }
            }
            return null;
        }

        /** 获取前置空白字符 **/
        private static String getLeadingWhitespace(CharSequence string) {
            for (int i = 0; i < string.length(); i++) {
                char c = string.charAt(i);
                if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
                    continue;
                } else {
                    return i == 0 ? "" : string.toString().substring(0, i);
                }
            }
            return string.toString();
        }

        /** 获取指定字符之前的位置 **/
        private static int getIndexOfBeforeTrailingChars(CharSequence string, char... chars) {
            int lastIndex = string.length() - 1;
            for (int i = lastIndex; i >= 0; i--) {
                char c = string.charAt(i);
                if (Text.isInChars(c, chars)) {
                    lastIndex--; // 移除最后连续的换行符
                } else {
                    break;
                }
            }
            return lastIndex;
        }

        /** 是不是全都是空白字符 **/
        private static boolean isAllWhitespace(CharSequence string) {
            for (int i = 0; i < string.length(); i++) {
                char c = string.charAt(i);
                if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
                    continue;
                } else {
                    return false;
                }
            }
            return true;
        }
    }

    public abstract static class Text {

        // 符号: ASCII码表顺序, 去掉了'`_$
        private static final char[] SYMBOLS = "\t\r\n !\"#%&()*+,-./:;<=>?@[\\]^{|}~".toCharArray();

        /**
         * 是不是SQL符号
         * 
         * @param c 指定字符
         * @return 是不是符号
         */
        private static boolean isSqlSymbol(char c, char... excludeSymbols) {
            if (isInChars(c, excludeSymbols)) {
                return false;
            }
            for (int i = 0; i < SYMBOLS.length; i++) {
                if (c == SYMBOLS[i]) {
                    return true;
                }
            }
            return false;
        }

        private static boolean isInChars(char c, char... chars) {
            if (chars != null && chars.length > 0) {
                for (int i = 0; i < chars.length; i++) {
                    if (c == chars[i]) {
                        return true;
                    }
                }
            }
            return false;
        }

        /** 统计文本中有多少个换行符 **/
        public static int countNewlineChars(SqlBuffer sql) {
            if (sql.items().isEmpty()) {
                return 0;
            }
            int count = 0;
            for (Item item : sql.items()) {
                if (item == null) {
                    continue;
                }
                CharSequence string;
                if (item instanceof StringItem) {
                    string = ((StringItem) item).getValue();
                } else if (item instanceof RawValueItem) {
                    string = ((RawValueItem) item).getValue();
                } else if (item instanceof VariableItem) {
                    continue;
                } else if (item instanceof OmitItem) {
                    continue;
                } else {
                    throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
                }
                if (string == null || string.length() == 0) {
                    continue;
                }
                count += IndentTools.countNewlineChars(string);
            }
            return count;
        }

        /**
         * 判断是不是以空行开头, 即判断前几个字符是不是换行符加空白
         * 
         * @param sql SQL语句
         * @return 是不是以空行开头
         */
        public static boolean startsWithBlankline(SqlBuffer sql) {
            return startsWithBlankline(sql, false);
        }

        /**
         * 判断是不是以空行开头, 即判断前几个字符是不是换行符加空白
         * 
         * @param sql SQL语句
         * @param allBlankValue 全部空白算不算以空行开头
         * @return 是不是以空行开头
         */
        public static boolean startsWithBlankline(SqlBuffer sql, boolean allBlankValue) {
            if (sql.items().isEmpty()) {
                return allBlankValue;
            }
            int size = sql.items().size();
            // 从前往后找, 空白字符继续; 先遇到换行符返回true, 先遇到非空白字符返回false
            for (int i = 0; i < size; i++) {
                Item item = sql.items().get(i);
                if (item instanceof StringItem || item instanceof RawValueItem) {
                    String string;
                    if (item instanceof StringItem) {
                        string = ((StringItem) item).getValue().toString();
                    } else {
                        string = ((RawValueItem) item).getValue();
                    }
                    for (int s = 0; s < string.length(); s++) {
                        char c = string.charAt(s);
                        if (c == ' ' || c == '\t') {
                            continue;
                        }
                        return c == '\r' || c == '\n';
                    }
                } else if (item instanceof VariableItem) {
                    return false; // 遇到变量, 返回false
                } else if (item instanceof OmitItem) {
                    continue; // 继续查找
                } else {
                    throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
                }
            }
            return allBlankValue; // 从前到后都是空白
        }

        /**
         * 判断是不是以空行结尾, 即判断最后是不是换行符加空白
         * 
         * @param sql SQL语句
         * @return 是不是以空行结尾
         */
        public static boolean endsWithBlankline(SqlBuffer sql) {
            return endsWithBlankline(sql, false);
        }

        /**
         * 判断是不是以空行结尾, 即判断最后是不是换行符加空白
         * 
         * @param sql SQL语句
         * @param allBlankValue 全部空白算不算以空行结尾
         * @return 是不是以空行结尾
         */
        public static boolean endsWithBlankline(SqlBuffer sql, boolean allBlankValue) {
            if (sql.items().isEmpty()) {
                return allBlankValue;
            }
            int size = sql.items().size();
            // 从后往前找, 空白字符继续; 先遇到换行符返回true, 先遇到非空白字符返回false
            for (int i = size - 1; i >= 0; i--) {
                Item item = sql.items().get(i);
                if (item instanceof StringItem || item instanceof RawValueItem) {
                    String string;
                    if (item instanceof StringItem) {
                        string = ((StringItem) item).getValue().toString();
                    } else {
                        string = ((RawValueItem) item).getValue();
                    }
                    for (int s = string.length() - 1; s >= 0; s--) {
                        char c = string.charAt(s);
                        if (c == ' ' || c == '\t') {
                            continue;
                        }
                        return c == '\r' || c == '\n';
                    }
                } else if (item instanceof VariableItem) {
                    return false; // 遇到变量, 返回false
                } else if (item instanceof OmitItem) {
                    continue; // 继续查找
                } else {
                    throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
                }
            }
            return allBlankValue; // 从后一直找到第1个字符都是空白
        }

        /** 是不是以SQL符号开头 **/
        public static boolean startsWithSqlSymbol(CharSequence string, char... excludeSymbols) {
            if (string == null || string.length() == 0) {
                return false;
            }
            return isSqlSymbol(string.charAt(0), excludeSymbols);
        }

        /** 是不是以SQL符号开头 **/
        public static boolean startsWithSqlSymbol(SqlBuffer sql, char... excludeSymbols) {
            if (sql.items().isEmpty()) {
                return false;
            }
            for (Item item : sql.items()) {
                if (item instanceof StringItem) {
                    return startsWithSqlSymbol(((StringItem) item).getValue(), excludeSymbols);
                } else if (item instanceof VariableItem) {
                    return false;
                } else if (item instanceof RawValueItem) {
                    return startsWithSqlSymbol(((RawValueItem) item).getValue(), excludeSymbols);
                } else if (item instanceof OmitItem) {
                    continue;
                } else {
                    throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
                }
            }
            return false;
        }

        /** 是不是以SQL符号结尾 **/
        public static boolean endsWithSqlSymbol(CharSequence string, char... excludeSymbols) {
            if (string == null || string.length() == 0) {
                return false;
            }
            return isSqlSymbol(string.charAt(string.length() - 1), excludeSymbols);
        }

        /** 是不是以SQL符号结尾 **/
        public static boolean endsWithSqlSymbol(SqlBuffer sql, char... excludeSymbols) {
            if (sql.items().isEmpty()) {
                return false;
            }
            for (int i = sql.items().size() - 1; i >= 0; i--) {
                Item item = sql.items().get(i);
                if (item instanceof StringItem) {
                    return endsWithSqlSymbol(((StringItem) item).getValue(), excludeSymbols);
                } else if (item instanceof VariableItem) {
                    return false;
                } else if (item instanceof RawValueItem) {
                    return endsWithSqlSymbol(((RawValueItem) item).getValue(), excludeSymbols);
                } else if (item instanceof OmitItem) {
                    continue;
                } else {
                    throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
                }
            }
            return false;
        }

        /** 是不是以指定字符开头 **/
        public static boolean startsWithChar(CharSequence string, char c) {
            if (string == null || string.length() == 0) {
                return false;
            }
            return c == string.charAt(0);
        }

        /** 是不是以指定字符开头 **/
        public static boolean startsWithChar(SqlBuffer sql, char c) {
            if (sql.items().isEmpty()) {
                return false;
            }
            for (Item item : sql.items()) {
                if (item instanceof StringItem) {
                    return startsWithChar(((StringItem) item).getValue(), c);
                } else if (item instanceof VariableItem) {
                    return false;
                } else if (item instanceof RawValueItem) {
                    return startsWithChar(((RawValueItem) item).getValue(), c);
                } else if (item instanceof OmitItem) {
                    continue;
                } else {
                    throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
                }
            }
            return false;
        }

        /** 是不是以指定字符结尾 **/
        public static boolean endsWithChar(CharSequence string, char c) {
            if (string == null || string.length() == 0) {
                return false;
            }
            return c == string.charAt(string.length() - 1);
        }

        /** 是不是以指定字符结尾 **/
        public static boolean endsWithChar(SqlBuffer sql, char c) {
            if (sql.items().isEmpty()) {
                return false;
            }
            for (int i = sql.items().size() - 1; i >= 0; i--) {
                Item item = sql.items().get(i);
                if (item instanceof StringItem) {
                    return endsWithChar(((StringItem) item).getValue(), c);
                } else if (item instanceof RawValueItem) {
                    return endsWithChar(((RawValueItem) item).getValue(), c);
                } else if (item instanceof VariableItem) {
                    return false;
                } else if (item instanceof OmitItem) {
                    continue;
                } else {
                    throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
                }
            }
            return false;
        }

        /**
         * 判断SQL中是否存在指定的关键字
         * 
         * @param sql SQL语句
         * @param keyword 指定的关键字: 如果是单词, 按整词匹配; 如果包含符号或空格, 按contains匹配
         * @return 是否存在
         */
        public static boolean exists(SqlBuilder sql, String keyword) {
            return exists(sql.out(), keyword, false);
        }

        /**
         * 判断SQL中是否存在指定的关键字, 只会从SQL文件中查找, 不会查找SQL变量
         * 
         * @param sql SQL语句
         * @param keyword 指定的关键字: 如果是单词, 按整词匹配; 如果包含符号或空格, 按contains匹配
         * @return 是否存在
         */
        public static boolean exists(SqlBuilder sql, String keyword, boolean skipLeadingBlank) {
            return exists(sql.out(), keyword, skipLeadingBlank);
        }

        /**
         * 判断SQL中是否存在指定的关键字
         * 
         * @param sql SQL语句
         * @param keyword 指定的关键字: 如果是单词, 按整词匹配; 如果包含符号或空格, 按contains匹配
         * @return 是否存在
         */
        public static boolean exists(SqlBuffer sql, String keyword) {
            return exists(sql, keyword, false);
        }

        /**
         * 判断SQL中是否存在指定的关键字, 只会从SQL文件中查找, 不会查找SQL变量
         * 
         * @param sql SQL语句
         * @param keyword 指定的关键字: 如果是单词, 按整词匹配; 如果包含符号或空格, 按contains匹配
         * @return 是否存在
         */
        public static boolean exists(SqlBuffer sql, String keyword, boolean skipLeadingBlank) {
            VerifyTools.requireNotBlank(keyword, "keyword");
            if (sql.isEmpty()) {
                return false;
            }
            boolean isword = StringTools.isWordString(keyword);
            boolean first = true;
            for (Item item : sql.items()) {
                if (item == null) {
                    continue;
                }
                String stringValue;
                if (item instanceof StringItem) {
                    stringValue = ((StringItem) item).getValue().toString();
                } else if (item instanceof RawValueItem) {
                    stringValue = ((RawValueItem) item).getValue();
                } else if (item instanceof OmitItem) {
                    continue;
                } else {
                    first = false;
                    continue;
                }
                if (stringValue.length() == 0) {
                    continue;
                }
                if (first && skipLeadingBlank) {
                    stringValue = StringTools.trimLeft(stringValue);
                }
                first = false;
                if (isword) {
                    if (StringTools.existsWord(stringValue, keyword)) {
                        return true;
                    }
                } else {
                    if (stringValue.contains(keyword)) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
}
